using System;
using System.Collections.Generic;
using System.Data;
using System.Text;

namespace Developex.DatabaseWrapper.Fields
{
	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="T">Child object type</typeparam>
	/// <typeparam name="AlternativePrimaryKeyType"></typeparam>
	public class ChildValuesField_pk<T, AlternativePrimaryKeyType> : IChildValuesField where T : Row, new()
	{
		private class ChildObjectList : RowList<T>
		{
			private readonly IField keyField;
			private readonly string foreignFieldName;

			public ChildObjectList(IField keyField, string foreignFieldName)
			{
				this.keyField = keyField;
				this.foreignFieldName = foreignFieldName;
			}

			public event EventHandler ChangeQueryBuilder = null;

			protected override QueryBuilder GetQueryBuilder(Connection conn)
			{
				QueryBuilder qb = base.GetQueryBuilder(conn);
				IDataParameter par = conn.CreateParameter(keyField.Name, keyField, keyField.GetValue());
				qb.Parameters.Add(par);
				qb.Where.Add(string.Format("{0} = {1}", foreignFieldName, par.ParameterName));
				if (ChangeQueryBuilder != null)
					ChangeQueryBuilder(qb, EventArgs.Empty);
				return qb;
			}
		}

		/// <summary>
		/// Items
		/// </summary>
		public Dictionary<AlternativePrimaryKeyType, T> Items = new Dictionary<AlternativePrimaryKeyType, T>();

		#region Constructors

		private Row ownerObject = null;
		private readonly string foreignFieldName;
		private readonly string alternativePrimaryKeyName;
		private string keyFieldName = null;
		private readonly bool isWeHavePrimary = false;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignFieldName"></param>
		/// <param name="alternativePrimaryKeyName"></param>
		public ChildValuesField_pk(string foreignFieldName, string alternativePrimaryKeyName)
			: this(null, foreignFieldName, alternativePrimaryKeyName)
		{
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="keyFieldName"></param>
		/// <param name="foreignFieldName"></param>
		/// <param name="alternativePrimaryKeyName"></param>
		public ChildValuesField_pk(string keyFieldName, string foreignFieldName, string alternativePrimaryKeyName)
		{
			this.foreignFieldName = foreignFieldName;
			this.alternativePrimaryKeyName = alternativePrimaryKeyName;
			this.keyFieldName = keyFieldName;
			T item = new T();
			isWeHavePrimary = item.PrimaryKeyFields.Length == 2 &&
				 ((item.PrimaryKeyFields[0].Name == foreignFieldName && item.PrimaryKeyFields[1].Name == alternativePrimaryKeyName) ||
				  (item.PrimaryKeyFields[1].Name == foreignFieldName && item.PrimaryKeyFields[0].Name == alternativePrimaryKeyName));
		}

		#endregion

		#region Interface implementation

		private bool readOnly = false;

		/// <summary>
		/// Is this field read-only
		/// </summary>
		public bool ReadOnly
		{
			get { return readOnly; }
			set { readOnly = value; }
		}

		private bool isInitialized = false;

		/// <summary>
		/// Is child values loaded
		/// </summary>
		public bool IsInitialized
		{
			get
			{
				return isInitialized || Items.Count > 0;
			}
			set { isInitialized = value; }
		}

		/// <summary>
		/// Init field
		/// </summary>
		/// <param name="owner"></param>
		public virtual void Init(Row owner)
		{
			ownerObject = owner;
			if (string.IsNullOrEmpty(keyFieldName))
			{
				keyFieldName = owner.PrimaryKeyField.Name;
			}
		}

		#endregion

		#region Update/Insert/Delete/Load

		/// <summary>
		/// sender is QueryBuilder object
		/// </summary>
		public event EventHandler ChangeLoadQueryBuilder = null;

		/// <summary>
		/// Fires after object loaded
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnLoad(Connection conn)
		{
			isInitialized = false;
			Items.Clear();
			ChildObjectList childObjects = new ChildObjectList(ownerObject.Fields[keyFieldName], foreignFieldName);
			childObjects.ChangeQueryBuilder += childObjects_ChangeQueryBuilder;
			if (!childObjects.Load(conn))
			{
				ownerObject.LastException = childObjects.LastException;
				return OperationResult.ExceptionOccured;
			}
			foreach (T item in childObjects.Items)
			{
				Items.Add((AlternativePrimaryKeyType)item.Fields[alternativePrimaryKeyName].GetValue(), item);
			}
			isInitialized = true;
			return OperationResult.Success;
		}

		private void childObjects_ChangeQueryBuilder(object sender, EventArgs e)
		{
			if (ChangeLoadQueryBuilder != null)
				ChangeLoadQueryBuilder(sender, e);
		}

		/// <summary>
		/// Fires after object updated
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnUpdate(Connection conn)
		{
			isInitialized = false;

			IField keyField = ownerObject.Fields[keyFieldName];
			object keyFieldValue = keyField.GetValue();

			// update items
			foreach (KeyValuePair<AlternativePrimaryKeyType, T> pair in Items)
			{
				T item = pair.Value;
				item.Fields[foreignFieldName].SetValue(keyFieldValue);
				item.Fields[alternativePrimaryKeyName].SetValue(pair.Key);
				OperationResult result;
				// TODO: check using if WillDoInserting!
				if (isWeHavePrimary || !item.WillDoInserting())
					result = item.Update(conn);
				else
				{
					result = item.UpdateByFields(conn, item.Fields[foreignFieldName], item.Fields[alternativePrimaryKeyName]);
					if (result == OperationResult.Success)
						result = item.LoadByFields(conn, new[] { item.Fields[foreignFieldName], item.Fields[alternativePrimaryKeyName] });
				}
				if (result == OperationResult.NothingForUpdate)
					result = item.Insert(conn);
				if (result != OperationResult.Success)
				{
					ownerObject.LastException = item.LastException;
					return result;
				}
			}

			// delete all other items
			T sampleItem = new T();
			QueryBuilder qb = new QueryBuilder(QueryBuilderType.Delete, sampleItem.TableName);

			IDataParameter parOwnerId = conn.CreateParameter("keyField", keyField, keyFieldValue);
			qb.Parameters.Add(parOwnerId);
			qb.Where.Add(string.Format("{0} = {1}", foreignFieldName, parOwnerId.ParameterName));

			int i = 0;
			foreach (KeyValuePair<AlternativePrimaryKeyType, T> pair in Items)
			{
				T item = pair.Value;
				i++;
				string cond = "";
				for (int k = 0; k < item.PrimaryKeyFields.Length; k++)
				{
					IDataParameter par = conn.CreateParameter("par" + i + "R" + k, item.PrimaryKeyFields[k], item.PrimaryKeyFields[k].GetValue());
					qb.Parameters.Add(par);
					if (k > 0)
						cond += "AND";
					cond += string.Format("({0} = {1})", item.PrimaryKeyFields[k].Name, par.ParameterName);
				}
				qb.Where.Add("NOT (" + cond + ")");
			}

			conn.ExecuteNonQuery(qb);

			isInitialized = true;

			return OperationResult.Success;
		}

		/// <summary>
		/// Fires after object inserted
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnInsert(Connection conn)
		{
			isInitialized = false;

			IField keyField = ownerObject.Fields[keyFieldName];
			object keyFieldValue = keyField.GetValue();

			foreach (KeyValuePair<AlternativePrimaryKeyType, T> pair in Items)
			{
				T item = pair.Value;
				item.Fields[foreignFieldName].SetValue(keyFieldValue);
				item.Fields[alternativePrimaryKeyName].SetValue(pair.Key);
				OperationResult result = item.Insert(conn);
				if (result != OperationResult.Success)
				{
					ownerObject.LastException = item.LastException;
					return result;
				}
			}

			isInitialized = true;

			return OperationResult.Success;
		}

		/// <summary>
		/// Fires before object delete
		/// </summary>
		/// <param name="conn"></param>
		/// <returns></returns>
		public virtual OperationResult OnBeforeDelete(Connection conn)
		{
			return OperationResult.Success;
		}

		/// <summary>
		/// Fires after object delete
		/// </summary>
		/// <param name="conn"></param>
		public OperationResult OnDelete(Connection conn)
		{
			return OperationResult.Success;
		}

		#endregion
	}
}
